package com.ruicar.afs.cloud.filecenter.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruicar.afs.cloud.common.core.exception.AfsBaseException;
import com.ruicar.afs.cloud.common.data.lock.anno.Lock;
import com.ruicar.afs.cloud.filecenter.config.FileConfigCache;
import com.ruicar.afs.cloud.filecenter.config.FileProperties;
import com.ruicar.afs.cloud.filecenter.entity.AfsFileBlackList;
import com.ruicar.afs.cloud.filecenter.entity.AfsFileChunkInfo;
import com.ruicar.afs.cloud.filecenter.entity.AfsFileInfo;
import com.ruicar.afs.cloud.filecenter.entity.AfsFileStore;
import com.ruicar.afs.cloud.filecenter.enums.ProcessStatus;
import com.ruicar.afs.cloud.filecenter.mapper.AfsFileBlackListMapper;
import com.ruicar.afs.cloud.filecenter.mapper.FileChunkInfoMapper;
import com.ruicar.afs.cloud.filecenter.service.IFileChunkService;
import com.ruicar.afs.cloud.filecenter.service.IFileInfoService;
import com.ruicar.afs.cloud.filecenter.service.IFileProcess;
import com.ruicar.afs.cloud.filecenter.service.IFileStoreService;
import com.ruicar.afs.cloud.filecenter.service.impl.processor.FinishToOssProcessor;
import com.ruicar.afs.cloud.filecenter.vo.FileStoreVo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * FileProcess
 * <p>Description: </p>
 *
 * @author Fzero
 * @version 1.0
 * @date create on 2020-05-29 14:50:45
 * @since 1.0
 */
@Service
@Data
@Slf4j
@AllArgsConstructor
public class FileProcess implements IFileProcess {
    /**
     * File store service
     * <p>Description: </p>
     */
    private final IFileStoreService iFileStoreService;
    /**
     * File chunk service
     * <p>Description: </p>
     */
    private final IFileChunkService iFileChunkService;
    /**
     * File info service
     * <p>Description: </p>
     */
    private final IFileInfoService fileInfoService;
    /**
     * File properties
     * <p>Description: </p>
     */
    private final FileProperties fileProperties;
    /**
     * Afs file black list mapper
     * <p>Description: </p>
     */
    private final AfsFileBlackListMapper afsFileBlackListMapper;

    private final FileChunkInfoMapper fileChunkInfoMapper;
    /**
     * Check file size boolean
     * <p>Description: </p>
     *
     * @param fileName file name
     * @param fileSize file size
     * @return the boolean
     */
    @Override
    public boolean checkFileSize(String fileName, long fileSize) {
        if(FileUtil.extName(fileName)==null){
            return false;
        }else if(FileConfigCache.ALLOW_FILE_SIZE.containsKey(FileUtil.extName(fileName).toUpperCase())){
            return FileConfigCache.ALLOW_FILE_SIZE.get(FileUtil.extName(fileName).toUpperCase())>=fileSize;
        }else if(!FileConfigCache.ALLOW_FILE_SIZE.containsKey(FileUtil.extName(fileName).toUpperCase())){
            return FileConfigCache.ALLOW_FILE_SIZE.getOrDefault("*",FileConfigCache.DEFAULT_AFS_FILE_SIZE)>=fileSize;
        }
        return false;
    }

    /**
     * Check file signature boolean
     * <p>Description: </p>
     *
     * @param fileName  file name
     * @param fileMd5   file md 5
     * @param signature signature
     * @return the boolean
     */
    @Override
    public boolean checkFileSignature(String fileName, String fileMd5, String signature) {
        if(afsFileBlackListMapper.selectById(fileMd5)!=null) {
            return Boolean.FALSE;
        }
        if(FileUtil.extName(fileName)==null
            ||FileConfigCache.BLACK_FILE_TYPE.contains(FileUtil.extName(fileName).toUpperCase())
        ){
            // 没有文件类型不允许上传
            //或者文件类型在黑名单
            return Boolean.FALSE;
        }


        long signatureMatchNum = FileConfigCache.BLACK_FILE_SIGNATURE.stream()
                .filter(s -> signature.toUpperCase().startsWith(s.toUpperCase()))
                .count();
        //特征码在黑名单中存在
        if(signatureMatchNum>0){
            try {
                AfsFileBlackList afsFileBlackList = new AfsFileBlackList();
                afsFileBlackList.setFileMd5(fileMd5);
                afsFileBlackList.setSignature(signature);
                afsFileBlackListMapper.insert(afsFileBlackList);
            }catch (Exception e){
                log.warn("文件md5{}已存在",fileMd5);
            }
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    /**
     * Merge file file store vo
     * <p>Description: </p>
     *
     * @param md5  md 5
     * @param guid guid
     * @return the file store vo
     */
    @Override
    @Lock(keys = "#md5",keyConstant = ":file-merge")
    public FileStoreVo mergeFile(String md5, String guid) {
        AfsFileStore afsFileStore = iFileStoreService.getOne(Wrappers.<AfsFileStore>lambdaQuery().eq(AfsFileStore::getChunkedId,md5));
        AfsFileInfo afsFileInfo =fileInfoService.getOne(Wrappers.<AfsFileInfo>lambdaQuery().eq(AfsFileInfo::getGuid,guid));
        if(afsFileStore==null){
            throw new AfsBaseException("请求错误");
        }
        if(afsFileStore.getProcessStatus().equals(ProcessStatus.UPLOADING.getCode())) {
            FileConfigCache.GLOBAL_PRE_FILE_PROCESSOR.stream().forEach(processor->{
                processor.process(md5,guid);
            });
            FileConfigCache.FILE_PRE_PROCESSOR.getOrDefault(afsFileInfo.getFileType().toUpperCase(),new ArrayList<>())
                    .stream()
                    .forEach(processor->{
                processor.process(md5,guid);
            });



            List<AfsFileChunkInfo> afsFileChunkInfoList = fileChunkInfoMapper.queryByMd5(md5);
            byte[] checkByte =  new byte[64];
            StringBuffer checkStr = new StringBuffer();
            if(fileProperties.isUseDb()){
                if(CollectionUtil.isEmpty(afsFileChunkInfoList)){
                    log.error("文件校验失败,文件片段为空");
                    return FileStoreVo.builder().key("").name("").fileMd5("").fileType("").build();
                }
            }else{
                try (InputStream inputStream=FileUtil.getInputStream(fileProperties.getTempDir() + File.separator + afsFileChunkInfoList.get(0).getChunkedFilePath())){
                    inputStream.read(checkByte,0,64);
                } catch (IOException e) {
                    log.error("文件校验失败",e);
                    return FileStoreVo.builder().key("").name("").fileMd5("").fileType("").build();
                }
            }

            try{
                for (int index = 0; index < checkByte.length; index++) {
                    log.debug("校验 {}--{}--{}",checkByte[index],((checkByte[index] >>> 4)&0x0F),checkByte[index]&0x0F);
                    checkStr.append(Integer.toHexString((checkByte[index] >>> 4)&0x0F));
                    checkStr.append(Integer.toHexString(checkByte[index]&0x0F));
                }
                if(!this.checkFileSignature(afsFileInfo.getFileName(),md5,checkStr.toString().toUpperCase())){
                    if(!fileProperties.isUseDb()) {
                        afsFileChunkInfoList.forEach(fileChunkInfo -> {
                            FileUtil.del(fileProperties.getTempDir() + File.separator + fileChunkInfo.getChunkedFilePath());
                        });
                    }
                    iFileStoreService.removeById(afsFileStore.getId());
                    iFileChunkService.lambdaUpdate().eq(AfsFileChunkInfo::getChunkedId,md5).remove();
                    return FileStoreVo.builder().key("").name("").fileMd5("").fileType("").build();
                }
            }catch (Exception e){
                log.error("文件校验失败",e);
                return FileStoreVo.builder().key("").name("").fileMd5("").fileType("").build();
            }



            File file = FileUtil.newFile(fileProperties.getBasePath() + File.separator + afsFileStore.getFilePath());
            afsFileChunkInfoList.forEach(fileChunkInfo -> {
                if(!fileProperties.isUseDb()) {
                    byte[] bytes = FileUtil.readBytes(fileProperties.getTempDir() + File.separator + fileChunkInfo.getChunkedFilePath());
                    FileUtil.writeBytes(bytes, file, 0, bytes.length, true);
                    FileUtil.del(fileProperties.getTempDir() + File.separator + fileChunkInfo.getChunkedFilePath());
                }else {
                    byte[] bytes = fileChunkInfo.getChunkedValue();
                    FileUtil.writeBytes(bytes, file, 0, bytes.length, true);
                }
            });
            afsFileStore.setProcessStatus(ProcessStatus.FINISH.getCode());
            iFileStoreService.updateById(afsFileStore);
            FileConfigCache.FILE_POST_PROCESSOR.getOrDefault(afsFileInfo.getFileType().toUpperCase(),new ArrayList<>())
                    .stream()
                    .forEach(processor->{
                        processor.process(md5,guid);
                    });
            FileConfigCache.GLOBAL_POST_FILE_PROCESSOR.stream().forEach(processor->{
                processor.process(md5,guid);
            });
            //后置处理器处理完成才删除所有分片信息
            iFileChunkService.lambdaUpdate().eq(AfsFileChunkInfo::getChunkedId,md5).remove();
        }
        return FileStoreVo.builder().key(afsFileInfo.getAccessKey()).name(afsFileInfo.getFileName())
                .fileMd5(md5).fileType(afsFileInfo.getFileType()).build();
    }

    /**
     * Delete file boolean
     * <p>Description: </p>
     *
     * @param accessKey access key
     * @return the boolean
     */
    @Override
    public Boolean deleteFile(String accessKey) {
        fileInfoService.remove(Wrappers.<AfsFileInfo>lambdaQuery().eq(AfsFileInfo::getAccessKey,accessKey));
        return Boolean.TRUE;
    }
}
